﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using dotnet_etcd;
using Etcdserverpb;
using Fetches.Fetch.Dto;
using Fetches.Fetch.Factory;
using Fetches.Fetch.Factory.Persistence;
using Fetches.Fetch.Utils;
using Google.Protobuf;
using Mvccpb;
using Newtonsoft.Json;
using Serilog;

namespace FetchesDeploy.Distributed.DataPersistence
{
    public class EtcdRuntimeDataService : IRuntimeDataService
    {
        public const int LeaseTtl = 10;
        public const string KeyForFactoryUnit = "/fetches/unit/";
        public const string KeyForFactoryConfig = "/fetches/config/factory";
        public const string KeyForFetchTask = "/fetches/fetch/";

        private readonly EtcdClient _etcdClient;

        //多个key可以共用一个Lease(不需要多次LeaseGrant、LeaseKeepAlive)，到期后多个key的值都会删除
        //对同一个key的多次写入不影响lease
        private LeaseGrantResponse _lease;

        public EtcdRuntimeDataService(string etcdAddress)
        {
            if (string.IsNullOrEmpty(etcdAddress))
            {
                etcdAddress = "http://localhost:2379";
            }

            _etcdClient = new EtcdClient(etcdAddress);
        }

        public FactoryConfig ReadFactoryConfig()
        {
            var data = _etcdClient.GetVal(KeyForFactoryConfig);
            return JsonConvert.DeserializeObject<FactoryConfig>(data);
        }

        public void WriteFactoryConfig(FactoryConfig config)
        {
            _etcdClient.Put(KeyForFactoryConfig, JsonConvert.SerializeObject(config));
        }

        public void Rebuild()
        {
            _etcdClient.DeleteRange("/fetches");
            WriteFactoryConfig(JsonFileHelper.FromFile<FactoryConfig>("FactoryConfig.json"));
        }

        public IList<UnitSchedules> LoadAllUnitSchedules()
        {
            var workshops = new List<UnitSchedules>();
            var workshopData = _etcdClient.GetRangeVal(KeyForFactoryUnit);
            foreach (var item in workshopData)
            {
                try
                {
                    var scheduleInfo = JsonConvert.DeserializeObject<UnitSchedules>(item.Value);
                    workshops.Add(scheduleInfo);
                }
                catch (Exception)
                {
                    //
                }
            }

            return workshops;
        }

        /// <summary>
        ///     因为FactoryUnit启动成功后必然会调用这个api更新调度信息
        ///     所以这个api兼顾更新注调度信息和心跳两种功能，心跳功能正常应该拆分的
        ///     现在的设计控制中心依赖WatchRange监控调度信息Key的变化来获得调度和心跳保持
        ///     还有一种设计可能性是FactoryUnit按心跳周期调用这个api，而不是由任务的分配引起调用
        /// </summary>
        /// <param name="schedules"></param>
        public void UpdateWorkshopScheduleData(UnitSchedules schedules)
        {
            if (_lease == null)
            {
                _lease = _etcdClient.LeaseGrant(new LeaseGrantRequest { ID = 0, TTL = LeaseTtl });
                _etcdClient.LeaseKeepAlive(_lease.ID, default);
            }

            var request = new PutRequest
            {
                Lease = _lease.ID,
                Key = ByteString.CopyFromUtf8($"{KeyForFactoryUnit}{schedules.Id}"),
                Value = ByteString.CopyFromUtf8(JsonConvert.SerializeObject(schedules))
            };
            _etcdClient.Put(request);
        }

        public IList<FetchTask> LoadUnfinishedFetchTask()
        {
            var fetchTasks = new List<FetchTask>();
            var data = _etcdClient.GetRangeVal(KeyForFetchTask);
            foreach (var item in data)
            {
                try
                {
                    var task = JsonConvert.DeserializeObject<FetchTask>(item.Value);
                    fetchTasks.Add(task);
                }
                catch (Exception)
                {
                    //
                }
            }

            return fetchTasks;
        }

        public FetchTask GetFetchTaskByTaskId(long taskId)
        {
            try
            {
                var taskVal = _etcdClient.GetVal($"{KeyForFetchTask}{taskId}");
                return JsonConvert.DeserializeObject<FetchTask>(taskVal);
            }
            catch (Exception e)
            {
                Log.Debug("EtcdRuntimeDataService got an error while getFetchTaskByTaskId {id},exception {exception}", taskId, e);
                return null;
            }
        }

        public FetchTask GetFetchTaskByFetcherId(long fetcherId)
        {
            try
            {
                var taskId = _etcdClient.GetVal($"/fetches/fetchers/{fetcherId}");
                var taskVal = _etcdClient.GetVal($"{KeyForFetchTask}{taskId}");
                return JsonConvert.DeserializeObject<FetchTask>(taskVal);
            }
            catch (Exception e)
            {
                Log.Debug("EtcdRuntimeDataService got an error while getFetchTaskByFetcherId {id},exception {exception}", fetcherId, e);
                return null;
            }
        }

        public void RegisterOrUpdateFetchTask(FetchTask task)
        {
            _etcdClient.Put($"{KeyForFetchTask}{task.Id}", JsonConvert.SerializeObject(task));
            //写入fetcherId到taskId的映射关系供GetFetchTaskByFetcherId使用
            foreach (var fetcherId in task.FetcherIds)
            {
                _etcdClient.Put($"/fetches/fetchers/{fetcherId}", task.Id.ToString());
            }
        }

        public void UpdateFetchTaskStatus(long taskId, FetchTaskStatus status)
        {
            var key = $"{KeyForFetchTask}{taskId}";
            if (status == FetchTaskStatus.Solved || status == FetchTaskStatus.Canceled)
            {
                //先删fetcherId到taskId的映射关系再删除task
                var taskVal = _etcdClient.GetVal(key);
                var task = JsonConvert.DeserializeObject<FetchTask>(taskVal);
                foreach (var fetcherId in task.FetcherIds)
                {
                    _etcdClient.Delete($"/fetches/fetchers/{fetcherId}");
                }
                _etcdClient.Delete(key);
            }
        }

        #region 待废弃

        public void WatchFetcherDispatch(UnitSchedules workshop, Action<long> action)
        {
            var taskKey = $"/fetches/fetchWork/{workshop.Id}";
            Log.Information("Workshop is watching key {key} for fetch work", taskKey);
            new Thread(() =>
            {
                var webClient = new WebClient();
                _etcdClient.Watch(taskKey, response =>
                {
                    foreach (var @event in response.Events.Where(e => e.Type == Event.Types.EventType.Put))
                    {
                        var val = @event.Kv.Value.ToStringUtf8();
                        HttpClient.Post($"{workshop.Endpoint}/fetcher/{val}", "", out _);
                    }

                    _etcdClient.Delete(taskKey);
                });
            }).Start();
        }

        #endregion
    }
}